#include "../include/Water.h"

#include "../include/Functions.h"
#define BUFFER_OFFSET(x) ((char*)NULL+(x))

Water::Water(std::string wFilename, GLuint shaderProg)
    : filename(wFilename),
	  imageLoaded(false),
	  width(0),
	  height(0),
      timeCounter(0),
      shaderProg(shaderProg)
{
    scale.x = 1;
    scale.y = 1;
    scale.z = 1;
    position.x = 0;
    position.y = 0;
    position.z = 0;
	load();
}

Water::~Water(){
	delete[] vertices;
	delete[] indices;
	delete[] topImage;
	delete[] farImage;
	delete[] bottomImage;
	delete[] rearImage;
	delete[] leftImage;
	delete[] rightImage;
}

/*
 *	Loads the Water Map into a VBO ready for rendering
 */
void Water::load(){
    //Load Water Height Map from file
    if(!heightMap.loadFromFile(filename)){
        exit(-1);
    }
    waterID = CG::loadTexture(filename);

	imageLoaded = true;

	/*Set the width and height of the Water*/
	width = heightMap.getSize().x;
	height = heightMap.getSize().y;

	/*Allocate the arrays for the Water and the noramls*/
	numVertices = width*height;
	int numTriangles = 2*(numVertices-width-height+1);
	vertices = new Vertex[numVertices];
	noIndices = 3*numTriangles;
	indices = new GLuint[noIndices];
	normalsByPosition = new std::list<Normal*>[width*height];

	for(int z = 0 ; z < height; z++){
		for (int x = 0;x < width; x++) {
			// compute the height as a value between 0.0 and 1.0
			GLfloat pointHeight = (float)(heightMap.getPixel(x,z)).r /255;
			vertices[x + z*width].position[0] = x;
			vertices[x + z*width].position[1] = 0; // pointHeight;
			vertices[x + z*width].position[2] = ((float)z)-height; //Working into -Z direction
		}
	}

	//Calculate the indices for each face of the mesh
	for(int i = 0, v = 0; i < (3*numTriangles) && numTriangles >= 2; i += 6) {
		indices[i] = v;
		indices[i+1] = v+1+width;
		indices[i+2] = v+1;
		indices[i+3] = v;
		indices[i+4] = v+width;
		indices[i+5] = v+width+1;
		if((v+1)%width == (width-1)) {
			v += 2;
		} else {
			v++;
		}
	}

	//Texture coord positions
	for(int x = 0; x < width; x++) {
		for(int z = 0; z < height; z++) {
            vertices[x+(z*width)].texCoords[0] = (1.0/width)*x;
			vertices[x+(z*width)].texCoords[1] = (1.0/height)*z;
		}
	}

	//We now have an array of Vertices
	//Send vertex data to GPU
	glGenBuffers(1, &vboID);
	glBindBuffer(GL_ARRAY_BUFFER, vboID);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*numVertices, NULL, GL_STATIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex)*numVertices, vertices);
	//Send index data to GPU
	glGenBuffers(1, &indicesVBOID);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBOID);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*noIndices, NULL, GL_STATIC_DRAW);
	glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(GLuint)*noIndices, indices);

	//load the skybox as the reflection map

    if(!loadReflectionMap("resources/images/stormydays_large.jpg")) {
        exit(-1);
    }
    /*
    if(!loadReflectionMap("resources/images/violentdays_large.jpg")) {
        exit(-1);
    }*/
	configTexture();
}

GLboolean Water::loadReflectionMap(std::string rFileName){

	/*Initialise the Image arrays*/

	topImage = new GLubyte[1024*1024*3];
	farImage = new GLubyte[1024*1024*3];
	bottomImage = new GLubyte[1024*1024*3];
	rearImage = new GLubyte[1024*1024*3];
	leftImage = new GLubyte[1024*1024*3];
	rightImage = new GLubyte[1024*1024*3];

	waterRef = new sf::Image();
	/*If the file does not load, Exit the program (Change later to print debug message)*/

	if( !waterRef->loadFromFile(rFileName)){
		exit(-1);
	}

	//glGenTextures(6,texID);

	loadRefImages();

	glGenTextures(1, &texID);
	glBindTexture(GL_TEXTURE_CUBE_MAP, texID);
	glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,topImage );
	glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,rightImage );
	glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,leftImage );
	glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,bottomImage );
	glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,farImage );
	glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 3,1024,1024,0, GL_RGB, GL_UNSIGNED_BYTE,rearImage );

	delete waterRef;
    delete[] topImage;
    delete[] bottomImage;
    delete[] rightImage;
    delete[] leftImage;
    delete[] farImage;
    delete[] rearImage;
	return true;
}

void Water::loadRefImages(){
	//For Each image, put all the pixels into the correct array
	for(int pixelY = 0; pixelY < 1024 ; pixelY++ ){
		for(int pixelX = 0; pixelX < 1024; pixelX++){
			//Top
			topImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY).r;
			topImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel((pixelX)+1024,pixelY).g;
			topImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY).b;
			//Left
			leftImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX,pixelY+1024).r;
			leftImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel(pixelX,pixelY+1024).g;
			leftImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX,pixelY+1024).b;
			//Far
			farImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+1024).r;
			farImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+1024).g;
			farImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+1024).b;
			//Right
			rightImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX+2048,pixelY+1024).r;
			rightImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel(pixelX+2048,pixelY+1024).g;
			rightImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX+2048,pixelY+1024).b;
			//Rear
			rearImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX+3072,pixelY+1024).r;
			rearImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel(pixelX+3072,pixelY+1024).g;
			rearImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX+3072,pixelY+1024).b;
			//Bottom
			bottomImage[(pixelX*3) + (pixelY*1024*3)] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+2048).r;
			bottomImage[(pixelX*3) + (pixelY*1024*3) + 1] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+2048).g;
			bottomImage[(pixelX*3) + (pixelY*1024*3) + 2] = (GLubyte)waterRef->getPixel(pixelX+1024,pixelY+2048).b;
		}
	}
}

void Water::configTexture() {
	glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_S,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_T,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_R,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

void Water::setScale(GLfloat x, GLfloat y, GLfloat z) {
    scale.x = x;
    scale.y = y;
    scale.z = z;
}

void Water::setPosition(GLfloat x, GLfloat y, GLfloat z) {
    position.x = x;
    position.y = y;
    position.z = z;
}

void Water::render(vec3 camPos){
	glUseProgram(shaderProg);
	GLint loc = glGetUniformLocation(shaderProg, "time");
	glUniform1f(loc, timeCounter);
	loc = glGetUniformLocation(shaderProg, "waterHeight");
	glUniform1f(loc, height);
	loc = glGetUniformLocation(shaderProg, "waterWidth");
    glUniform1f(loc, width);
    loc = glGetUniformLocation(shaderProg, "camPos");
    glUniform3f(loc, camPos.x , camPos.y, camPos.z);

	//If the Image is not loaded. Load it
	if(!imageLoaded)
		load();

    glEnable(GL_TEXTURE_2D);
    CG::setTextureUnit(shaderProg, "tex", 0);
    CG::bindTexture(waterID, GL_TEXTURE0);

	glPushMatrix();
    glTranslatef(position.x, position.y, position.z);
    glScalef(scale.x, scale.y, scale.z);
	glBindBuffer(GL_ARRAY_BUFFER, vboID);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBOID);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);
    glClientActiveTexture(GL_TEXTURE0);
    glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(24));
	glNormalPointer(GL_FLOAT, sizeof(Vertex),  BUFFER_OFFSET(12));
	glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));
	glDrawElements(GL_TRIANGLES, noIndices, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisable(GL_TEXTURE_2D);
	glPopMatrix();
}

std::string Water::getHMFileName(){
	return filename;
}
